package com.ku.kill.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ku.kill.dao.ItemDOMapper;
import com.ku.kill.dao.ItemStockDOMapper;
import com.ku.kill.dao.StockLogDOMapper;
import com.ku.kill.dataobject.ItemDO;
import com.ku.kill.dataobject.ItemStockDO;
import com.ku.kill.dataobject.StockLogDO;
import com.ku.kill.error.BusinessException;
import com.ku.kill.error.EmBusinessError;
import com.ku.kill.mq.MQProvider;
import com.ku.kill.service.CacheService;
import com.ku.kill.service.ItemService;
import com.ku.kill.service.PromoService;
import com.ku.kill.service.model.ItemModel;
import com.ku.kill.service.model.PromoModel;
import com.ku.kill.validator.ValidationResult;
import com.ku.kill.validator.ValidatorImpl;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.prefs.BackingStoreException;
import java.util.stream.Collectors;

/**
 * @program: kill2
 * @description:
 * @author: JokerKu
 * @create: 2020-04-02 22:28
 **/
@Service
public class ItemServiceImpl implements ItemService {

    @Autowired
    private ItemDOMapper itemDOMapper;
    @Autowired
    private ItemStockDOMapper itemStockDOMapper;//商品库存
    @Autowired
    private ValidatorImpl validator;
    @Autowired
    private PromoService promoService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private CacheService cacheService;

    @Autowired
    private MQProvider mqProvider;

    @Autowired
    private StockLogDOMapper stockLogDOMapper;

    @Transactional
    @Override
    public ItemModel createItem(ItemModel itemModel) throws BusinessException {
        System.out.println(itemModel.toString());
        //校验入参
        ValidationResult validate = validator.validate(itemModel);
        System.out.println("" + validate.isHasErrors());
        if (validate.isHasErrors()) {
            System.out.println("" + validate.isHasErrors());
            throw new BusinessException(EmBusinessError.PARMATER_VALIDATION_ERROR, validate.getErrMsg());
        }
        try {
            ItemDO itemDO = convertItemDOFromItemModel(itemModel);
            itemDOMapper.insertSelective(itemDO);

            itemModel.setId(itemDO.getId());
            ItemStockDO itemStockDO = convertItemStockDOFromItemModel(itemModel);
            itemStockDOMapper.insert(itemStockDO);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(EmBusinessError.UNKONMW_ERROR);
        }
        return this.getItemById(itemModel.getId());
    }

    @Override
    public List<ItemModel> listItem() {
        List<ItemDO> itemDOS = itemDOMapper.selectAll();

        //使用steam API将DoList集合转换成ModelList
        List<ItemModel> itemList = itemDOS.stream().map(itemDO -> {
            ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
            return covertItemModelFromDataObject(itemDO, itemStockDO);
        }).collect(Collectors.toList());
        return itemList;
    }

    @Override
    public ItemModel getItemById(Integer id) throws BusinessException {
        //根据主键查询商品
        ItemDO itemDO = itemDOMapper.selectByPrimaryKey(id);
        if (itemDO == null) {
            throw new BusinessException(EmBusinessError.ITEM_NOT_EXIT);
        }
        //获得库存数量
        ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
        //DO-->Model
        ItemModel itemModel = covertItemModelFromDataObject(itemDO, itemStockDO);
        //查询商品是否有活动
        PromoModel promoModel = promoService.getPromoByItemId(itemModel.getId());
        //有活动并且没有结束
        if (promoModel != null && promoModel.getStatus() != 3) {
            itemModel.setPromoModel(promoModel);
        }
        return itemModel;
    }

    //走缓存+分布式锁
    @Override
    @Transactional
    public ItemModel getItemWithCacheById(Integer id) throws BusinessException {
        ItemModel itemModel = null;

        //查询本地缓存
        itemModel = (ItemModel) cacheService.getCommonCache("item:" + id);
        if (itemModel != null) {
            return itemModel;
        }
        try {
            //查询redis
            String itemStr = redisTemplate.opsForValue().get("item:" + id);
            //判断缓存中是否为null
            if (StrUtil.isEmpty(itemStr)) {
                //为空：查询数据库
                //1.设置分布式锁防止缓存击穿
                final String token = IdUtil.fastSimpleUUID();
                Boolean isLock = redisTemplate.opsForValue().setIfAbsent("itemLock:" + id, token, 10, TimeUnit.SECONDS); //10s操作时间
                System.out.println("加锁结果：" + isLock);
                //成功上锁后，操作数据库
                if (isLock != null && isLock) {
                    //根据主键查询商品
                    ItemDO itemDO = itemDOMapper.selectByPrimaryKey(id);
                    if (itemDO != null) {
                        //获得库存数量
                        ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(itemDO.getId());
                        //DO-->Model
                        itemModel = covertItemModelFromDataObject(itemDO, itemStockDO);
                        //查询商品是否有活动
                        PromoModel promoModel = promoService.getPromoByItemId(itemModel.getId());
                        //有活动并且没有结束
                        if (promoModel != null && promoModel.getStatus() != 3) {
                            itemModel.setPromoModel(promoModel);
                        }
                        System.out.println("从数据库取");
                        //存放当本地缓存中
                        cacheService.setCommonCache("item:" + id, itemModel);
                        //存放到Redis中
                        redisTemplate.opsForValue().set("item:" + id, JSONUtil.toJsonStr(itemModel), 12, TimeUnit.HOURS);//12小时过期
                    } else {//数据库中也不存在，为了防止缓存穿透，将null或者空字符串设置给redis,并在一定时间内过期
                        redisTemplate.opsForValue().set("item:" + id, "", 3, TimeUnit.MINUTES);//3min过期
                    }
                    //删除锁
                    //可以用lua脚本，在查询到key的同时删除该key，防止高并发情况下的意外发生
                    final String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);//Long.class一定要写，不然会使用String类型做返回值，导致报错
                    CopyOnWriteArrayList<String> keyList = new CopyOnWriteArrayList<>();
                    keyList.add("itemLock:" + id);
                    // 参数一：redisScript，参数二：KEYS，参数三：ARGV（可多个）
                    redisTemplate.execute(redisScript, keyList, token);//Collections.singletonList(token)
                } else { //上锁失败，自旋3s
                    TimeUnit.SECONDS.sleep(3);
                    return getItemById(id);//返回到方法最开始处重新执行
                }
            } else { //缓存不为空
                System.out.println("从缓存中取");
                itemModel = JSONUtil.toBean(itemStr, ItemModel.class);
            }
        } catch (Exception e) {
            e.printStackTrace();
            //throw new BusinessException(EmBusinessError.ITEM_NOT_EXIT);
        }


        return itemModel;
    }

    /**
     * 扣减库存 ，在数据库中是加锁的串行操作 ——> 把库存放在缓存中 代码在promoService
     *
     * @param itemId
     * @param amount
     * @return
     */
    @Transactional
    @Override
    public boolean decreaseStock(Integer itemId, Integer amount) {
        Integer result = itemStockDOMapper.decreaseStock(itemId, amount);
        //更新库存成功 true 更新失败 false
        return result > 0;
    }

    //todo 从redis里减库存
    @Override
    public boolean decreaseStockInCache(Integer itemId, Integer amount) {
        //increment就是修改值，amount*-1就是在原来的基础上进行相减操作
        Long result = redisTemplate.opsForValue().increment("promo_item_stock" + itemId, amount * -1);
        if (result != null && result > 0) { //>=0表示库存余额>=0
            return true;
        } else if (result != null && result == 0) {
            //减库存成功 并打上售罄表示
            redisTemplate.opsForValue().set("promo_item_stock_invalid_" + itemId, "true");
            return true;
        } else {
            //更新库存失败
            //回滚
            increaseStock(itemId, amount);
            return false;
        }
    }

    //todo 将扣减数据库的库存方法提取出来
    @Override
    public boolean asyncDecreaseStock(Integer id, Integer amount) {
        return mqProvider.asyncReduceStock(id, amount);
    }

    @Override
    public void increaseStock(Integer itemId, Integer amount) {
        redisTemplate.opsForValue().increment("promo_item_stock" + itemId, amount);
    }

    /**
     * 当用户点击下单时 生成一个库存流水状态
     *
     * @param itemId
     * @param amount
     */
    @Override
    @Transactional
    public String initStockLog(Integer itemId, Integer amount) {
        StockLogDO stockLogDO = new StockLogDO();
        stockLogDO.setAmount(amount);
        stockLogDO.setItemId(itemId);
        stockLogDO.setStockLogId(IdUtil.fastSimpleUUID());//手动生成ID
        stockLogDO.setStatus(1);//1表示订单为初始状态

        stockLogDOMapper.insertSelective(stockLogDO);

        return stockLogDO.getStockLogId();
    }

    @Transactional
    @Override
    public void increaseSales(Integer itemId, Integer amount) {
        itemDOMapper.increaseSales(itemId, amount);
    }

//    @Override
//    public ItemModel getItemByIdInCache(Integer id) throws BusinessException {
//        String itemModelStr = redisTemplate.opsForValue().get("ITEM_VALIDATE:" + id);
//        ItemModel itemModel = null;
//        //redis中为空
//        if (StrUtil.isEmpty(itemModelStr)){
//            itemModel = this.getItemById(id);
//            redisTemplate.opsForValue().set("item_validate:" + id,JSONUtil.toJsonStr(itemModel));
//            return itemModel;
//        }
//        //不为空
//         itemModel = JSONUtil.toBean(itemModelStr,ItemModel.class,10,TimeUnit.MINUTES);
//        System.out.println("item走缓存");
//        return itemModel;
//    }


    //model-->DO
    private ItemDO convertItemDOFromItemModel(ItemModel itemModel) {
        if (itemModel == null) {
            return null;
        }
        ItemDO itemDO = new ItemDO();
        BeanUtil.copyProperties(itemModel, itemDO);
        return itemDO;
    }

    //model-->DO
    private ItemStockDO convertItemStockDOFromItemModel(ItemModel itemModel) {

        if (itemModel == null) {
            return null;
        }
        ItemStockDO itemStockDO = new ItemStockDO();
        itemStockDO.setItemId(itemModel.getId());
        itemStockDO.setStock(itemModel.getStock());
        return itemStockDO;
    }

    //DO-->model
    private ItemModel covertItemModelFromDataObject(ItemDO itemDO, ItemStockDO itemStockDO) {
        if (itemDO == null) {
            return null;
        }
        ItemModel model = new ItemModel();
        BeanUtil.copyProperties(itemDO, model);
        if (itemStockDO != null) {
            model.setStock(itemStockDO.getStock());
        }
        return model;
    }
}
